<?php

/**
 * XML
 * @author ReGeDa
 */
class XML extends Plugin_Object {

	/**
	 * @var DOMDocument
	 */
	public $DOM;

	/**
	 * @var DOMXPath
	 */
	private $XPath;

	/**
	 * @var DOMElement
	 */
	private $root_element;

	/**
	 * Constructor
	 */
	public function __construct() {

		platcode()->plugins->include_class('static/ArrayStream');

		// create DOM
		$this->DOM = new DOMDocument('1.0','UTF-8');

		// create XPath
		$this->XPath = new DOMXPath($this->DOM);

		// create root
		$this->DOM->appendChild(
			$this->root_element = $this->DOM->createElement('platcode')
		);
	}

	/**
	 * Append a child with value
	 * @param DOMNode $parent
	 * @param string $node_name
	 * @param mixed $value
	 */
	private function append_node($parent, $node_name, $value) {
		if ( ! empty($value) || gettype($value) == 'integer') {
			// a new XML node
			$node = $this->DOM->createElement($node_name);
			// body statement
			$this->convert($value, $node);
			$parent->appendChild($node);
		}
	}

	/**
	 * Convert array to XML string
	 * @param mixed $data
	 * @param DOMNode $contextnode
	 */
	private function convert($data, $contextnode) {
		// is array
		if (is_array($data)) {
			// not empty
			if ( ! empty($data)) {
				// hashed array
				if (ArrayStream::is_assoc($data)) {
					// loop
					foreach($data as $key => $value)
						$this->append_node($contextnode, $key, $value);
				}
				// ordered array
				else {
					foreach($data as $value)
						$this->append_node($contextnode, 'item', $value);
				}
			}
		}
		// is simple data type
		else {
			// append a new text node to context
			$contextnode->appendChild(
				$this->DOM->createTextNode($data)
			);
		}
	}

	/**
	 * XPath query
	 * @param string $expression
	 * @return DOMNodeList
	 */
	public function query($expression) {
		// for root
		if ($expression == 'ROOT')
			$expression = '//platcode';
		// otherwise
		return $this->XPath->query($expression);
	}

	/**
	 * Set attribute to entries from expression
	 * @param string $expression
	 * @param mixed $var
	 * @param string $value
	 */
	public function set_attributes($expression, $var, $value = '') {

		if (is_string($var))
			$var = array($var => $value);

		// query
		$entries = $this->query($expression);
		// for each entry
		foreach($entries as $entry) {
			// set attributes
			foreach($var as $name => $val)
				$entry->setAttribute($name, $val);
		}
	}

	/**
	 * Append converted xml string to node by XPath
	 * @param string $expression
	 * @param string $xml
	 * @param string $tag
	 */
	public function &import_xml($expression, $xml, $tag = 'custom') {
		// query
		$entries = $this->query($expression);

		// append
		foreach($entries as $entry) {
			$entry->appendChild(
				$this->DOM->importNode(
					DOMDocument::loadXML("<$tag>$xml</$tag>")->firstChild,
					TRUE
				)
			);
		}

		// return self
		return $this;
	}

	/**
	 * Serialize array data
	 * @param array $data
	 * @return string $expression
	 */
	public function &serialize($data, $expression = '') {
		// for xpath query
		if ( ! empty($expression)) {
			// query
			$entries = $this->query($expression);
			// for each entry
			foreach($entries as $entry)
				$this->convert($data, $entry);
		}
		// for root
		else
			$this->convert($data, $this->root_element);

		// return self
		return $this;
	}

	/**
	 * XSLT transformation
	 * @param string $path
	 * @param boolean $validate
	 * @return string
	 */
	public function transform($path, $validate = FALSE) {
		$path = PLATCODE_PATH.'templates/'.$path;

		// validation
		if ($validate && ! $this->DOM->schemaValidate($path.'.xsd'))
			throw new Platcode_Exception("Invalid XML in $path schema");

		// XSLT Processor
		$xslt = new XSLTProcessor();
		// register functions
		$xslt->registerPHPFunctions();
		// load XSL file
		$xslt->importStyleSheet(DOMDocument::load($path.'.xsl', $validate = FALSE));
		// transform
		return $xslt->transformToXml($this->DOM);
	}

}

// end of file